package hustlas.ball.preparse;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

public class PTable {
	private HashMap<String,ArrayList<String>> first;
	private HashMap<String,ArrayList<String>> follow;
	private GrammarGen grammar;
	HashMap<String,Boolean> epsilons;
	private HashMap<StringTokenTuple,String[]> tableTop;
	//TODO: make first and follow sets works or w/e
	public PTable(GrammarGen grammar) {
		this.grammar = grammar;
		
	}
	//this needs a token list
	public HashMap<StringTokenTuple,String[]> generateTable(ArrayList<Tokens> testTokens) {
		HashMap<StringTokenTuple,String[]> parseTable = new HashMap<StringTokenTuple,String[]>();
		for(ParseRule pRule : grammar.getParseList()) {
			for(String production : pRule.getRules()) {
				for(Tokens toke : testTokens) {
					if(checkTable(pRule.getName(), toke, production)) {
						parseTable.put(new StringTokenTuple(pRule.getName(),toke), production.split(" "));
					}
				}
			}
		}
		this.tableTop = parseTable;
		toFile();
		return parseTable;
	}
	
	public void toFile(){
	    String nut = "";
	    for(StringTokenTuple thing: tableTop.keySet()){
	        nut += "M" + thing + "-> " + tableTop.get(thing) + "\n";
	    }
	    BufferedWriter out;
        try {
            out = new BufferedWriter(new FileWriter("PTable.txt"));
            out.write(nut);
            out.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
	    //return out;
	}
	private boolean isToken(String suspect) {
		if(Tokens.getToken(suspect) == null) {
			if(suspect.equals("\u03F5")) {
				return true;
			}
			else {
				return false;
			}
		}
		else {
			return true;
		}
	}
	/**
	 * precondition: the mentioned rule has encountered an epsilon when copying over
	 * tokens 
	 * @param eName
	 * @param nonTermToRules
	 * @return 
	 */
	private String epsilonRules (String nonTerm, String epsilonString,HashMap<String,String[]> nonTermToRules) {
		String checkEp;
		String[] checkRule; 
				
		for(int i = 0; i < nonTermToRules.get(nonTerm).length; i++) {
			checkEp = nonTermToRules.get(nonTerm)[i];
			if(checkEp.contains(epsilonString)) {
				checkRule = checkEp.split(" ");
				for(int j = 0; j < checkRule.length; j ++) {
					if(checkRule.equals(epsilonString)) {
						if(j == checkRule.length - 1) {
							return "\u03F5";
						}
						else {
							return checkRule[j];
						}
					}
				}
			}
		}
		return null;
		
	}
	
	private HashMap<String,Tokens[]> convertToToken(HashMap<String,ArrayList<String>> set, String[] nonTerms){
		ArrayList<Tokens> token = new ArrayList<Tokens>();
		HashMap<String,Tokens[]> tokeUp = new HashMap<String, Tokens[]>();
		Tokens[] finalToke;
		
		/*for(int i = 0; i < nonTerms.length; i++) {
			for(int j = 0; j < set.get(nonTerms[i]).size(); j++) {
				if(Tokens.getToken(set.get(nonTerms[i]).get(j)) == null 
						&& set.get(nonTerms[i]).get(j).equals("\u03F5")) {
					token.add(Tokens.ϵ);
				}
				else if(Tokens.getToken(set.get(nonTerms[i]).get(j)) == null 
						&& set.get(nonTerms[i]).get(j).equals("\u03F5")){
					token.add(Tokens.$);
				}
				else {
					token.add(Tokens.getToken(set.get(nonTerms[i]).get(j)));
				}
			}
			finalToke = new Tokens[token.size()];
			
			finalToke = (Tokens[]) token.toArray();
			token.clear();
			tokeUp.put(nonTerms[i], finalToke);
			
		}*/
		Set<String> lol = set.keySet();
		for(String thing: set.keySet()){
		    String[] toke = new String[set.get(thing).size()];
		    set.get(thing).toArray(toke);
		    
		    
		    

		    
		    
		    Tokens[] ha = new Tokens[toke.length];
		    int count = 0;
		    for(String toker: toke){
		        if(Tokens.getToken(toker) == null && toker.equals("\u03F5")){
		            ha[count] = Tokens.ϵ;
		        } else if (Tokens.getToken(toker) == null && toker.equals("$")){
		            ha[count] = Tokens.$;
		        }
		        ha[count] = Tokens.getToken(toker);
		        count++;
		    }
		    tokeUp.put(thing, ha);
		}
		return tokeUp;
	}
	
	public HashMap<String,Tokens[]> makeFirstSet() {
		first = new HashMap<String,ArrayList<String>>();
		String[] nonTerms = new String[grammar.getParseList().size()];
		HashMap<String,String[]> nonTermToRules = new HashMap<String,String[]>();
		String[] fracturedRule;
		String[] ruleArray;
		for(int i = 0; i < grammar.getParseList().size(); i++) {
			nonTerms[i] = grammar.getParseList().get(i).getName();
		}
		
		for(int i = 0; i < nonTerms.length; i++) {
			nonTermToRules.put(nonTerms[i],grammar.getParseList().get(i).getRules());
			first.put(nonTerms[i], new ArrayList<String>());
		}
		
		for(int i = 0; i < nonTerms.length; i++) {
			ruleArray = nonTermToRules.get(nonTerms[i]);
			for(String singleRule : ruleArray) {
				fracturedRule = singleRule.split(" ");
				if(isToken(fracturedRule[0])){
					first.get(nonTerms[i]).add(fracturedRule[0]);
				}
				else {
					first.get(nonTerms[i]).addAll(firstSetHelper(nonTermToRules, fracturedRule[0]));
				}
			}
		}
		
		return convertToToken(first, nonTerms);
	}
	
	private ArrayList<String> firstSetHelper(HashMap<String,String[]> rules, String nonTerm) {
		//this thing just returns jrr tolkiens
		ArrayList<String> lilFirstSet = new ArrayList<String>();
		String[] ruleArray;
		String[] fracturedRule;
		ruleArray = rules.get(nonTerm);
		for(String singleRule : ruleArray) {
			fracturedRule = singleRule.split(" ");
			if(isToken(fracturedRule[0])){
				if(fracturedRule[0].equals("\u03F5")) {
					//special case need passed in rule//lilFirstSet.addAll(firstSetHelper)
					lilFirstSet.addAll(firstSetEpsilonHelper(rules,fracturedRule[0],fracturedRule,0));
				}
				else {
					lilFirstSet.add(fracturedRule[0]);
				}
			}
			else {
				lilFirstSet.addAll(firstSetHelper(rules, fracturedRule[0]));
			}
		}
		return lilFirstSet;
	}
	
	private ArrayList<String> firstSetEpsilonHelper(HashMap<String,String[]> rules, String nonTerm, String[] rule, int curpos) {
		ArrayList<String> lilFirstSet = new ArrayList<String>();
		String[] ruleArray;
		String[] fracturedRule;
		ruleArray = rules.get(nonTerm);
		for(String singleRule : ruleArray) {
			fracturedRule = singleRule.split(" ");
			if(isToken(fracturedRule[0])){
				if(fracturedRule[0].equals("\u03F5")) {
					//special case need passed in rule//lilFirstSet.addAll(firstSetHelper)
					if(curpos == rule.length - 1) {
						lilFirstSet.add("\u03F5");
					}
					else {
						lilFirstSet.addAll(firstSetEpsilonHelper(rules,rule[++curpos],rule,curpos));
					}
				}
				else {
					lilFirstSet.add(fracturedRule[0]);
				}
			}
			else {
				lilFirstSet.addAll(firstSetHelper(rules, fracturedRule[0]));
			}
		}
		return lilFirstSet;
	}

	//TODO: implement follow sets
	public HashMap<String,Tokens[]> makeFollowSet() {
	    follow = new HashMap<String,ArrayList<String>>();
        epsilons = new HashMap<String,Boolean>();
        String[] nonTerms = new String[grammar.getParseList().size()];
        HashMap<String, Integer> ruleIndex = new HashMap<String, Integer>();
        for(int i = 0; i < grammar.getParseList().size(); i++) {
            nonTerms[i] = grammar.getParseList().get(i).getName();
        }
        for(int i = 0; i < grammar.getParseList().size(); i++) {
            for(int j = 0; j < grammar.getParseList().get(i).getRules().length; j++) {
                if(grammar.getParseList().get(i).getRules()[j].contains("\u03F5")){
                    epsilons.put(nonTerms[i], true);
                }
            }
        }
		for(int i = 0; i < grammar.getParseList().size(); i++) {
			nonTerms[i] = grammar.getParseList().get(i).getName();
		}
		
        for(int i = 0; i < grammar.getParseList().size(); i++) {
            ruleIndex.put(grammar.getParseList().get(i).getName(), i);
            follow.put(grammar.getParseList().get(i).getName(), new ArrayList<String>());
        }
        int size = 0;
        int size1 = -1;
        while(size1 != size){
            size1 = size;
            for(int i = 0; i < grammar.getParseList().size(); i++){
                String name = grammar.getParseList().get(i).getName();
                if(i == 0){
                    ArrayList<String> followSet = follow.get(name);
                    //Add check to see if it's already in the arraylist
                    if(!followSet.contains("$")){
                        followSet.add("$");
                        size++;
                        follow.put(name, followSet);
                    }
                }
                for(int j = 0; j < grammar.getParseList().size(); j++){
                    for(String rule: grammar.getParseList().get(j).getRules()){
                        String[] exp = rule.split(" ");
                        for(int k = 0; k < exp.length; k++){
                            if(exp[k].equals(name)){
                                //we found it! Now we check to see what is after it.
                                if(k == exp.length - 1){
                                    ArrayList<String> followSet = follow.get(name);
                                    //Add check to see if it's already in the arraylist
                                    if(follow.containsKey(grammar.getParseList().get(j).getName())){
                                        for(String item: follow.get(grammar.getParseList().get(j).getName())){
                                            
                                            if(!followSet.contains(item)){
                                                followSet.add(item);
                                                size++;
                                                follow.put(name, followSet);
                                            }
                                        }
                                    }
                                } else if(follow.containsKey(exp[k + 1])){
                                    ArrayList<String> followSet = follow.get(name);
                                    /*for(String item: first.get(exp[k+1])){
                                        if(!followSet.contains(item)){
                                            followSet.add(item);
                                            size++;
                                            follow.put(name, followSet);
                                        }
                                    }*/
                                    if(epsilons.get(exp[k+1]) == true){
                                        for(String item: follow.get(exp[k+1])){
                                            if(!followSet.contains(item)){
                                                followSet.add(item);
                                                size++;
                                                follow.put(name, followSet);
                                            }
                                        }
                                    }
                                } else {
                                    String item = exp[k+1];
                                    ArrayList<String> followSet = follow.get(name);
                                    if(!followSet.contains(item)){
                                        followSet.add(item);
                                        size++;
                                        follow.put(name, followSet);
                                    }
                                }
                            }
                        }
                    }
                }
                
            }
        }
        
        return convertToToken(follow,nonTerms);
	}
	/**
	 * 
	 * @param nonTerminal A
	 * @param terminal a
	 * @param w the rule to check
	 * @return whether or not the rule A->w is in the table T[A,a]
	 */
	public boolean checkTable(String nonTerminal, Tokens terminal, String w) {
	    if (first.get(w) == null) {
	    	if (terminal.toString().equals(w)) {
	    		return true;
	    	} else {
	    		return false;
	    	}
	    }
		if(first.get(w).contains(terminal.toString())){
	        return true;
	    } else if (epsilons.get(w) != null){
	        if(follow.get(nonTerminal).contains(terminal.toString())){
	            return true;
	        }
	    } 
        return false;
	}
}
